# InheritanceMetadataResolver.java

    package org.codefilarete.stalactite.engine.configurer.dslresolver;

    import java.util.Collection;
    import java.util.Map;
    import java.util.Set;

    import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration;
    import org.codefilarete.stalactite.dsl.naming.ForeignKeyNamingStrategy;
    import org.codefilarete.stalactite.engine.configurer.model.AncestorJoin;
    import org.codefilarete.stalactite.engine.configurer.model.DirectRelationJoin;
    import org.codefilarete.stalactite.engine.configurer.model.Entity;
    import org.codefilarete.stalactite.engine.configurer.model.Entity.AbstractPropertyMapping;
    import org.codefilarete.stalactite.engine.configurer.model.ExtraTableJoin;
    import org.codefilarete.stalactite.engine.configurer.model.Mapping;
    import org.codefilarete.stalactite.engine.configurer.model.PropertyMappingHolder;
    import org.codefilarete.stalactite.engine.configurer.dslresolver.InheritanceConfigurationResolver.ResolvedConfiguration;
    import org.codefilarete.stalactite.engine.configurer.dslresolver.MetadataSolvingCache.EntitySource;
    import org.codefilarete.stalactite.sql.ConnectionConfiguration;
    import org.codefilarete.stalactite.sql.Dialect;
    import org.codefilarete.stalactite.sql.ddl.structure.ForeignKey;
    import org.codefilarete.stalactite.sql.ddl.structure.KeyMapping;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.tool.collection.KeepOrderMap;
    import org.codefilarete.tool.collection.KeepOrderSet;
    import org.codefilarete.tool.function.Hanger;

    import static org.codefilarete.tool.collection.Iterables.first;

    /**
     * Look up an {@link EntityMappingConfiguration} inheritance hierarchy and creates an {@link Entity} instance from it
     * with the following information set:
     * - identifier mapping
     * - property-to-column bindings
     * - joins for extra tables (secondary tables)
     * - all ancestors
     *
     * This is the first step of creating a fully configured {@link Entity} instance. The next ones are:
     * - relation
     * - polymorphism
     */
    public class InheritanceMetadataResolver<C, I, T extends Table<T>> {

    	private final Dialect dialect;
    	private final ConnectionConfiguration connectionConfiguration;

    	public InheritanceMetadataResolver(Dialect dialect, ConnectionConfiguration connectionConfiguration) {
    		this.dialect = dialect;
    		this.connectionConfiguration = connectionConfiguration;
    	}

    	/**
    	 * Resolves the inheritance hierarchy of the given entity configuration into a ready-to-use
    	 * {@link Entity} graph.
    	 *
    	 * @param entityConfiguration the configuration to transform into an {@link Entity} instance
    	 * @return an {@link Entity} instance, filled with identifier and ancestors information
    	 */
    	Entity<C, I, T> resolve(EntityMappingConfiguration<C, I> entityConfiguration) {
    		InheritanceConfigurationResolver<C, I> inheritanceConfigurationResolver = new InheritanceConfigurationResolver<>();
    		KeepOrderSet<ResolvedConfiguration<?, I>> bottomToTopConfigurations = inheritanceConfigurationResolver.resolveConfigurations(entityConfiguration);
    		return resolve(bottomToTopConfigurations).getEntity();
    	}

    	/**
    	 * Resolves the inheritance hierarchy of the given entity configuration into a ready-to-use
    	 * {@link Entity} graph.
    	 *
    	 * @param bottomToTopConfigurations the whole configuration hierarchy, ordered from bottom to top, the first one will be transformed into an {@link Entity} instance
    	 * @return an {@link Entity} instance, filled with identifier and ancestors information
    	 */
    	EntitySource<C, I> resolve(KeepOrderSet<ResolvedConfiguration<?, I>> bottomToTopConfigurations) {
    		KeyMappingApplier<C, I> keyMappingApplier = new KeyMappingApplier<>(dialect, connectionConfiguration);
    		keyMappingApplier.resolve(bottomToTopConfigurations);

    		return resolveHierarchy(bottomToTopConfigurations);
    	}

    	<X, TT extends Table<TT>> EntitySource<C, I> resolveHierarchy(KeepOrderSet<ResolvedConfiguration<?, I>> bottomToTopConfigurations) {
    		// Handling very first entity as a seed for eventual next iterations on the hierarchy
    		ResolvedConfiguration<C, I> bottomestConfiguration = (ResolvedConfiguration<C, I>) first(bottomToTopConfigurations);
    		T bottomTable = (T) bottomestConfiguration.getTable();
    		Entity<C, I, T> bottomestEntity = this.buildEntity(bottomestConfiguration, bottomTable);
    		EntitySource<C, I> bottomestEntitySource = new EntitySource<>(bottomestEntity, bottomestConfiguration);

    		final Hanger.Holder<TT> previousTable = new Hanger.Holder<>((TT) bottomTable);
    		final Hanger.Holder<Entity<X, I, TT>> previousEntity = new Hanger.Holder<>((Entity<X, I, TT>) bottomestEntity);
    		bottomToTopConfigurations.stream().skip(1)
    				.map(((Class<ResolvedConfiguration<X, I>>) (Class) ResolvedConfiguration.class)::cast)
    				.forEach(resolvedConfigurationPawn -> {
    					TT resolvedTable = (TT) resolvedConfigurationPawn.getTable();
    					if (previousTable.get() != resolvedTable) {
    						Entity<X, I, TT> ancestor = buildEntity(resolvedConfigurationPawn, resolvedTable);
    						bottomestEntitySource.addSource(ancestor, resolvedConfigurationPawn);
    						// We could use the foreign key between previousTable and resolvedTable that has been created by the KeyMappingApplier instance
    						// used during buildEntity(..) logic, but the foreign key is not exposed, thus we mimic it through the primary keys (should be enhanced)
    						previousEntity.get().setParent(new AncestorJoin<>(ancestor, new DirectRelationJoin<>(previousTable.get().getPrimaryKey(), resolvedTable.getPrimaryKey())));
    						// preparing next iteration
    						previousTable.set(resolvedTable);
    						previousEntity.set(ancestor);
    					} else {
    						addMapping(previousEntity.get(), resolvedConfigurationPawn, previousTable.get());
    						bottomestEntitySource.addSource(previousEntity.get(), resolvedConfigurationPawn);
    					}
    				});
    		return bottomestEntitySource;
    	}

    	private <X, TT extends Table<TT>> Entity<X, I, TT> buildEntity(ResolvedConfiguration<X, I> configuration, TT table) {
    		Entity<X, I, TT> result = new Entity<>(configuration.getIdentifierMapping(), new Mapping<>(configuration.getMappingConfiguration().getEntityType(), table));
    		addMapping(result, configuration, table);
    		return result;
    	}

    	private <X, TT extends Table<TT>, EXTRATABLE extends Table<EXTRATABLE>> void addMapping(Entity<X, I, TT> entity,
    	                                                                                         ResolvedConfiguration<X, I> configuration,
    	                                                                                         TT table) {
    		PropertyMappingResolver<X, TT> propertyMappingResolver = new PropertyMappingResolver<>(dialect.getColumnBinderRegistry());
    		Set<AbstractPropertyMapping<X, ?, TT>> mapping = propertyMappingResolver.resolve(
    				configuration.getMappingConfiguration().getPropertiesMapping(),
    				table,
    				configuration.getNamingConfiguration().getColumnNamingStrategy());

    		Set<AbstractPropertyMapping<X, ?, EXTRATABLE>> extraTableMappings = new KeepOrderSet<>();
    		mapping.forEach(mappingPawn -> {
    			if (mappingPawn.getColumn().getTable() == table) {
    				entity.getPropertyMappingHolder().addMapping(mappingPawn);
    			} else {
    				extraTableMappings.add((AbstractPropertyMapping<X, ?, EXTRATABLE>) mappingPawn);
    			}
    		});
    		addExtraTableProperties(entity, extraTableMappings, configuration.getNamingConfiguration().getForeignKeyNamingStrategy());

    	}

    	private <X, TT extends Table<TT>, EXTRATABLE extends Table<EXTRATABLE>> void addExtraTableProperties(Entity<X, I, TT> result,
    	                                                                                                     Collection<AbstractPropertyMapping<X, ?, EXTRATABLE>> extraTableProperties,
    	                                                                                                     ForeignKeyNamingStrategy foreignKeyNamingStrategy) {
    		Map<EXTRATABLE, KeepOrderSet<AbstractPropertyMapping<X, ?, EXTRATABLE>>> propertiesPerTable = new KeepOrderMap<>();
    		extraTableProperties.forEach(mapping -> {
    			KeepOrderSet<AbstractPropertyMapping<X, ?, EXTRATABLE>> mappings = propertiesPerTable.computeIfAbsent(mapping.getColumn().getTable(), k -> new KeepOrderSet<>());
    			mappings.add(mapping);
    		});
    		propertiesPerTable.forEach((extraTable, mapping) -> {
    			PrimaryKeyPropagator<TT, EXTRATABLE, I> primaryKeyPropagator = new PrimaryKeyPropagator<>();
    			ForeignKey<EXTRATABLE, TT, I> foreignKey = primaryKeyPropagator.propagate(result.getTable().getPrimaryKey(), extraTable, foreignKeyNamingStrategy);
    			PropertyMappingHolder<X, EXTRATABLE> mappingHolder = new PropertyMappingHolder<>();
    			mappingHolder.addMapping(mapping);
    			KeyMapping<TT, EXTRATABLE, I> reference = foreignKey.getReferencedKey().reference(foreignKey);
    			ExtraTableJoin<X, TT, EXTRATABLE, I> relation = new ExtraTableJoin<>(mappingHolder, reference);
    			result.addRelation(relation);
    		});
    	}
    }